package edu.pku.sei.autounit.reflect.java;

import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import edu.pku.sei.autounit.annotation.Context;
import edu.pku.sei.autounit.annotation.AutoUnitMethod;
import edu.pku.sei.autounit.reflect.AbstractContextType;
import edu.pku.sei.autounit.reflect.Class;
import edu.pku.sei.autounit.reflect.Constructor;
import edu.pku.sei.autounit.reflect.Interface;
import edu.pku.sei.autounit.reflect.Method;
import edu.pku.sei.autounit.reflect.Type;
import edu.pku.sei.autounit.reflect.TypeParameter;

public class JavaClass extends AbstractContextType implements Class{

	
	private String simpleName;
	private String fullName;
	private String packageName;
	private Class supperClass;
	private Interface [] interfaces;
	private java.lang.Class<?> javaClass;
	private TypeFactory factory;
	private int mod;
	private Constructor[] contructors;
	
	public JavaClass(java.lang.Class<?> javaClass,TypeFactory factory){

		this.javaClass = javaClass;
		this.simpleName = javaClass.getSimpleName();
		this.fullName = javaClass.getName();
		this.packageName = javaClass.getPackage().getName();
		this.factory = factory;
		this.mod = javaClass.getModifiers();
		Context context = javaClass.getAnnotation(Context.class);
		Type [] contexts = null;
		if(context != null){
			java.lang.Class<?>[] cs= context.value();
			contexts = new Type[cs.length];
			for(int i=0;i<cs.length;i++)
				contexts[i] = factory.getType(cs[i], this);
		}
		else
			contexts = new Type[0];
		
		TypeVariable<?> [] tps = javaClass.getTypeParameters();
		typeParameters = new TypeParameter[tps.length];
		
		for(int i =0 ;i<tps.length;i++){
			typeParameters[i] = new JavaTypeParameter(tps[i].getName());
			if(i<contexts.length)
				typeParameters[i].setContext(contexts[i]);
			java.lang.reflect.Type []tt = tps[i].getBounds();
			Type [] bs = new Type[tt.length];
			for(int j = 0; j<tt.length; j++)
				bs[j] = factory.getType(tt[j],this);
			typeParameters[i].setBounds(bs);
		}
	}
	
	public JavaClass(java.lang.Class<?> javaClass,Type []contexts,TypeFactory factory){

		this.javaClass = javaClass;
		this.simpleName = javaClass.getSimpleName();
		this.fullName = javaClass.getName();
		this.packageName = javaClass.getPackage().getName();
		this.factory = factory;
		this.mod = javaClass.getModifiers();
		
		TypeVariable<?> [] tps = javaClass.getTypeParameters();
		typeParameters = new TypeParameter[tps.length];
		
		for(int i =0 ;i<tps.length;i++){
			typeParameters[i] = new JavaTypeParameter(tps[i].getName());
			if(i<contexts.length)
				typeParameters[i].setContext(contexts[i]);
			java.lang.reflect.Type []tt = tps[i].getBounds();
			Type [] bs = new Type[tt.length];
			for(int j = 0; j<tt.length; j++)
				bs[j] = factory.getType(tt[j],this);
			typeParameters[i].setBounds(bs);
		}
	}
	
	public JavaClass(ParameterizedType p,edu.pku.sei.autounit.reflect.Context con,TypeFactory factory){
		java.lang.Class<?> c = (java.lang.Class<?>)p.getRawType();
		this.javaClass = c;
		simpleName = javaClass.getSimpleName();
		fullName = javaClass.getName();
		packageName = javaClass.getPackage().getName();
		this.factory = factory;
		this.mod = javaClass.getModifiers();
		
		TypeVariable<?> [] tps = javaClass.getTypeParameters();
		typeParameters = new TypeParameter[tps.length];
		java.lang.reflect.Type [] contexts = p.getActualTypeArguments();
		for(int i =0 ;i<tps.length;i++){
			typeParameters[i] = new JavaTypeParameter(tps[i].getName());
			typeParameters[i].setContext(factory.getType(contexts[i],con));
			
			java.lang.reflect.Type []tt = tps[i].getBounds();
			Type [] bs = new Type[tt.length];
			for(int j = 0; j<tt.length; j++)
				bs[j] = factory.getType(tt[j],this);
			typeParameters[i].setBounds(bs);
		}
	}
	
	public String getName(Map<String, String> map){
		StringBuffer sb = new StringBuffer();
		if(fullName.startsWith("java.lang"))
			sb.append(simpleName);
		else{
			String f = map.get(simpleName);
			if(f == null){
				map.put(simpleName, fullName);
				sb.append(simpleName);
			}
			else if(f.equals(fullName))
				sb.append(simpleName);
			else
				sb.append(fullName);
		}

		Type [] c = getContexts();
		if(c.length == 0)
			return sb.toString();
		sb.append("<");
		for(int i=0;i<typeParameters.length-1;i++){
			sb.append(c[i].getName(map)+",");
		}
		sb.append(c[typeParameters.length-1].getName(map)+">");
		return sb.toString();
	}

	
	public Constructor[] getPublicConstructors() {
		if(contructors == null){
			List<Constructor> list = new ArrayList<Constructor>();
			for(java.lang.reflect.Constructor<?> c:javaClass.getConstructors()){
				if (!Modifier.isPublic(c.getModifiers()))
					continue;
				list.add(new JavaConstructor(this, c,factory));
			}
			contructors = list.toArray(new Constructor[list.size()]);
		}
		return contructors;
	}

	
	public Method[] getTestMethods() {
		List<Method> methods = new ArrayList<Method>();
		for (java.lang.reflect.Method im : javaClass.getDeclaredMethods()) {
			if (!Modifier.isPublic(im.getModifiers()))
				continue;
			if (im.getAnnotation(AutoUnitMethod.class) == null)
				continue;
			methods.add(new JavaMethod(this, im,factory));
		}
		return methods.toArray(new Method[methods.size()]);
	}
	
	
	public Method[] getPublicMethods() {
		List<Method> methods = new ArrayList<Method>();
		for (java.lang.reflect.Method im : javaClass.getDeclaredMethods()) {
			if (!Modifier.isPublic(im.getModifiers()))
				continue;
			methods.add(new JavaMethod(this, im,factory));
		}
		return methods.toArray(new Method[methods.size()]);
	}

	
	public String getFullName() {
		return fullName;
	}

	
	public String getPackageName() {
		return packageName;
	}

	
	public Iterator<Class> getSubClasses() {
		return new Iterator<Class>(){

			
			public boolean hasNext() {
				return false;
			}

			
			public Class next() {
				return null;
			}

			
			public void remove() {
			}
			
		};
	}
	
	
	public Class getSuperClass() {
		if(supperClass == null){
			if(fullName.equals(Object.class.getName()))
				this.supperClass = null;
			else
				this.supperClass = (Class) factory.getType(javaClass.getGenericSuperclass(),this);
		}
		return supperClass;
	}

	
	public Interface[] getInterfaces() {
		if(interfaces == null){
			java.lang.reflect.Type []is = javaClass.getGenericInterfaces();
			interfaces = new Interface[is.length];
			for(int i=0;i<is.length;i++){
				interfaces[i] = (Interface) factory.getType(is[i],this);
			}
		}
		return interfaces;
	}

	
	public boolean canInstantiation() {
		if(!Modifier.isAbstract(mod)&&Modifier.isPublic(mod)&&
				this.getPublicConstructors().length!=0)
			return true;
		else
			return false;
	}
}
